package jlink

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
)

/*
#cgo windows CFLAGS: -DCGO_OS_WINDOWS=1
#cgo linux CFLAGS: -DCGO_OS_LINUX=1
#cgo linux LDFLAGS: -ldl


#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(CGO_OS_LINUX)

#include <dlfcn.h>

#elif defined(CGO_OS_WINDOWS)

#include <windows.h>

#define  RTLD_NOW   0

void * dlopen( const char *filename, int flags )
{
	HMODULE  hmod;

	hmod = LoadLibraryEx( filename, NULL, (DWORD)flags );
	return (void *)hmod;
}

void * dlsym( void * handle, const char *symbol )
{
	HMODULE  hmod;

	hmod = (HMODULE)handle;
	return (void *)GetProcAddress( hmod, (LPCSTR)symbol );
}

#else

# error    "unsupport os"

#endif



#define  JLINKARM_HOSTIF_USB	1
#define  JLINKARM_HOSTIF_IP		2

#define  JLINKARM_TIF_JTAG		0
#define  JLINKARM_TIF_SWD		1


#define  JLINK_ERR_EMU_NO_CONNECTION			(-256)	// No connection to emulator / Connection to emulator lost
#define  JLINK_ERR_EMU_COMM_ERROR				(-257)	// Emulator communication error, (host-interface module repORted error)
#define  JLINK_ERR_DLL_NOT_OPEN					(-258)	// DLL has not been opened but needs to be, (JLINKARM_Open() needs to be called first
#define  JLINK_ERR_VCC_FAILURE					(-259)	// Target system has no power (Measured VTref < 1V)
#define  JLINK_ERR_INVALID_HANDLE				(-260)	// File handle / memory area handle needed for operation, but given handle is not valid
#define  JLINK_ERR_NO_CPU_FOUND					(-261)	// Could not find supported CPU
#define  JLINK_ERR_EMU_FEATURE_NOT_SUPPORTED  	(-262)	// Emulator does not support the selected feature, Usually returned by functions which need specific emulator capabilities
#define  JLINK_ERR_EMU_NO_MEMORY				(-263)	// Emulator does not have enough memory to perform the requested operation
#define  JLINK_ERR_TIF_STATUS_ERROR				(-264)	// Things such as "TCK is low but should be high"
#define  JLINK_ERR_FLASH_PROG_COMPARE_FAILED  	(-265)
#define  JLINK_ERR_FLASH_PROG_PROGRAM_FAILED  	(-266)
#define  JLINK_ERR_FLASH_PROG_VERIFY_FAILED   	(-267)
#define  JLINK_ERR_OPEN_FILE_FAILED 			(-268)
#define  JLINK_ERR_UNKNOWN_FILE_FORMAT  		(-269)
#define  JLINK_ERR_WRITE_TARGET_MEMORY_FAILED 	(-270)


typedef struct _connect_info {
	uint32_t  SerialNumber;
	uint32_t  Connection;
	uint32_t  USBAddr;
	uint8_t  aIPAddr[16];
	int32_t  Time;
	uint64_t  Time_us;
	uint32_t  HWVersion;
	uint8_t  abMACAddr[6];
	int8_t  acProduct[32];
	int8_t  acNickName[32];
	int8_t  acFWString[112];
	int8_t  IsDHCPAssignedIP;
	int8_t  IsDHCPAssignedIPIsValid;
	int8_t  NumIPConnections;
	int8_t  NumIPConnectionsIsValid;
	uint8_t  aPadding[34];

} JLINKARM_EMU_CONNECT_INFO;


typedef void (* JLINKARM_LOG)( const char * slog );

typedef struct _context {
	void * phdl;

	// export funcs
	// 0-3
	uint32_t  (* JLINKARM_GetDLLVersion)( void );
	int (* JLINKARM_ExecCommand)( char * scmd, char * serr, int errsiz );
	int (* JLINKARM_EMU_GetList)( int HostIFs, JLINKARM_EMU_CONNECT_INFO * pInfo, int MaxInfos);
	int (* JLINKARM_EMU_SelectByUSBSN)( uint32_t SerialNo );

	// 4-5
	char * (*JLINKARM_OpenEx)( JLINKARM_LOG pfLog, JLINKARM_LOG pfErrorOut );
	void (* JLINKARM_Close)( void );

	// speed, khz
	// 6-8
	int (* JLINKARM_TIF_Select)( int Interface );
	void (* JLINKARM_SetSpeed)( uint32_t Speed );
	int (* JLINKARM_Connect)( void );

	// 9-10
	uint32_t (* JLINKARM_GetSelDevice)( void );
	uint32_t (* JLINKARM_GetId)( void );

	// 11-21
	int (* JLINKARM_Halt)( void );
	int (* JLINKARM_WaitForHalt)(int TimeOut);
	uint32_t (* JLINKARM_ReadReg)( uint32_t idx );
	int (* JLINKARM_WriteReg)( uint32_t idx, uint32_t data );
	int (* JLINKARM_WriteMem)( uint32_t addr, uint32_t cnt, void * pdat );
	int (* JLINKARM_ReadMem)( uint32_t addr, uint32_t cnt, void * pdat );
	void (* JLINKARM_GoIntDis)( void );
	void (* JLINKARM_Go)( void );
	int (* JLINKARM_Step)(void);
	int (* JLINKARM_WriteDCC)( uint32_t * pdat, uint32_t cnt, int TimeOut );
	int (* JLINKARM_ReadDCC)( uint32_t * pdat, uint32_t cnt, int TimeOut );

	// 22-23
	int (* JLINKARM_GetRegisterList)( uint32_t * paList, int MaxNumItems );
	char * (* JLINKARM_GetRegisterName)( uint32_t idx );

} jcontext;



int  jlkarm_load( char * epath, jcontext ** pret )
{
	int  i;
	char * tpath;
	void * phdl;
	jcontext * pctx;
	intptr_t * func;

	//
	tpath = (char *)malloc( strlen(epath) + 32 );
	if ( tpath == NULL )
	{
		return -1;
	}

	strcpy( tpath, epath );

#if defined(CGO_OS_LINUX)
	strcat( tpath, "/libjlinkarm.so" );
#else
	strcat( tpath, "\\JLink_x64.dll" );
#endif

	phdl = dlopen( tpath, RTLD_NOW );
	if ( phdl == NULL )
	{
#if defined(CGO_OS_LINUX)
		strcpy( tpath, "libjlinkarm.so" );
#else		
		strcpy( tpath, "JLink_x64.dll" );
#endif

		phdl = dlopen( tpath, RTLD_NOW );
		if ( phdl == NULL )
		{
			free( tpath );
			return -2;
		}
	}

	free( tpath );
	pctx = malloc( sizeof(jcontext) );
	if ( pctx == NULL )
	{
		return -3;
	}

	//
	pctx->JLINKARM_GetDLLVersion = (uint32_t(*)(void))dlsym( phdl, "JLINKARM_GetDLLVersion" );
	pctx->JLINKARM_ExecCommand = (int(*)(char*, char*, int))dlsym( phdl, "JLINKARM_ExecCommand" );
	pctx->JLINKARM_EMU_GetList = (int(*)(int, JLINKARM_EMU_CONNECT_INFO *, int))dlsym( phdl, "JLINKARM_EMU_GetList" );
	pctx->JLINKARM_EMU_SelectByUSBSN = (int(*)(uint32_t))dlsym( phdl, "JLINKARM_EMU_SelectByUSBSN" );
	pctx->JLINKARM_OpenEx = (char *(*)(JLINKARM_LOG, JLINKARM_LOG))dlsym( phdl, "JLINKARM_OpenEx" );
	pctx->JLINKARM_Close = (void (*)(void))dlsym( phdl, "JLINKARM_Close" );
	pctx->JLINKARM_TIF_Select = (int (*)(int))dlsym( phdl, "JLINKARM_TIF_Select" );
	pctx->JLINKARM_SetSpeed = (void (*)(uint32_t))dlsym( phdl, "JLINKARM_SetSpeed" );
	pctx->JLINKARM_Connect = (int (*)(void))dlsym( phdl, "JLINKARM_Connect" );
	pctx->JLINKARM_GetSelDevice = (uint32_t (*)(void))dlsym( phdl, "JLINKARM_GetSelDevice" );
	pctx->JLINKARM_GetId = (uint32_t (*)(void))dlsym( phdl, "JLINKARM_GetId" );
	pctx->JLINKARM_Halt = (int (*)(void))dlsym( phdl, "JLINKARM_Halt" );
	pctx->JLINKARM_WaitForHalt = (int (*)(int))dlsym( phdl, "JLINKARM_WaitForHalt" );
	pctx->JLINKARM_ReadReg = (uint32_t (*)(uint32_t))dlsym( phdl, "JLINKARM_ReadReg" );
	pctx->JLINKARM_WriteReg = (int (*)(uint32_t, uint32_t))dlsym( phdl, "JLINKARM_WriteReg" );
	pctx->JLINKARM_WriteMem = (int (*)(uint32_t, uint32_t, void *))dlsym( phdl, "JLINKARM_WriteMem" );
	pctx->JLINKARM_ReadMem = (int (*)(uint32_t, uint32_t, void *))dlsym( phdl, "JLINKARM_ReadMem" );
	pctx->JLINKARM_GoIntDis = (void (*)(void))dlsym( phdl, "JLINKARM_GoIntDis" );
	pctx->JLINKARM_Go = (void (*)(void))dlsym( phdl, "JLINKARM_Go" );
	pctx->JLINKARM_Step = (int (*)(void))dlsym( phdl, "JLINKARM_Step" );
	pctx->JLINKARM_WriteDCC = (int (*)(uint32_t *, uint32_t, int))dlsym( phdl, "JLINKARM_WriteDCC" );
	pctx->JLINKARM_ReadDCC = (int (*)(uint32_t *, uint32_t, int))dlsym( phdl, "JLINKARM_ReadDCC" );

	pctx->JLINKARM_GetRegisterList = (int (*)(uint32_t *, int))dlsym( phdl, "JLINKARM_GetRegisterList" );
	pctx->JLINKARM_GetRegisterName = (char * (*)(uint32_t))dlsym( phdl, "JLINKARM_GetRegisterName" );

	// pctx->xxx = ()dlsym( phdl, "xxx" );

	//
	func = (intptr_t *)&(pctx->JLINKARM_GetDLLVersion);
	for ( i=0; i<24; i++ )
	{
		if ( func[i] == 0 )
		{
			return (-1000 - i );
		}
	}

	//
	*pret = pctx;
	return 0;
}

static void  dummy_log( const char * slog )
{
	return;
}

int  jlkarm_open( jcontext * pctx )
{
	int  iret;
	char * perr;
	JLINKARM_EMU_CONNECT_INFO  infos[4];

	//
	iret = pctx->JLINKARM_EMU_GetList( JLINKARM_HOSTIF_USB, infos, 4 );
	if ( iret < 0 )
	{
		return -1;
	}

	if ( iret == 0 )
	{
		return -2;
	}

	//
	iret = pctx->JLINKARM_EMU_SelectByUSBSN( infos[0].SerialNumber );
	if ( iret < 0 )
	{
		return -3;
	}

	//
	perr = pctx->JLINKARM_OpenEx( dummy_log, dummy_log );
	if ( perr != NULL )
	{
		printf( "%s\n", perr );
		return -4;
	}

	//
	return 0;
}


int  jlkarm_connect( jcontext * pctx, uint32_t spd )
{
	int  iret;

	// swd, 4Mhz
	pctx->JLINKARM_TIF_Select( JLINKARM_TIF_SWD );
	pctx->JLINKARM_SetSpeed( spd );

	// device
	pctx->JLINKARM_ExecCommand( "device = CORTEX-A5", 0, 0 );

	// connect
	iret = pctx->JLINKARM_Connect();
	if ( iret < 0 )
	{
		return iret;
	}

	return 0;
}


void  jlkarm_close( jcontext * pctx )
{
	pctx->JLINKARM_Close();
	return;
}

uint32_t  jlkarm_getversion( jcontext * pctx )
{
	return pctx->JLINKARM_GetDLLVersion();
}


uint32_t  jlkarm_getdevice( jcontext * pctx )
{
	return pctx->JLINKARM_GetId();
}

int  jlkarm_halt( jcontext * pctx )
{
	int  iret;

	//
	iret = pctx->JLINKARM_Halt();
	if ( iret != 0 )
	{
		return -1;
	}

	iret = pctx->JLINKARM_WaitForHalt( 500 );
	if ( iret != 1 )
	{
		return -2;
	}

	return 0;
}

uint32_t jlkarm_readreg( jcontext * pctx, uint32_t tidx )
{
	return pctx->JLINKARM_ReadReg( tidx );
}

void  jlkarm_writereg( jcontext * pctx, uint32_t tidx, uint32_t data )
{
	pctx->JLINKARM_WriteReg( tidx, data );
	return;
}

int  jlkarm_readmem( jcontext * pctx, uint32_t addr, uint32_t cnt, void * pdat )
{
	return ( pctx->JLINKARM_ReadMem( addr, cnt, pdat ) );
}

int  jlkarm_writemem( jcontext * pctx, uint32_t addr, uint32_t cnt, void * pdat )
{
	return ( pctx->JLINKARM_WriteMem( addr, cnt, pdat ) );
}

void  jlkarm_gointdis( jcontext * pctx )
{
	pctx->JLINKARM_Go();
	return;
}

void  jlkarm_step( jcontext * pctx )
{
	int  iret;
	iret = pctx->JLINKARM_Step();
	printf( "step, %d\n", iret );
	return;
}


int  jlkarm_writedcc( jcontext * pctx, uint32_t cnt, void * pdat, uint32_t tms )
{
	int  iret;
	cnt = (cnt + 3) >> 2;
	iret = pctx->JLINKARM_WriteDCC( (uint32_t *)pdat, cnt, (int)tms );

	if ( iret < 0 )
	{
		return iret;
	}

	if ( (uint32_t)iret != cnt )
	{
		return -1000;
	}

	// 0 - success
	return 0;
}


int  jlkarm_readdcc( jcontext * pctx, uint32_t cnt, void * pdat, uint32_t tms )
{
	int  iret;
	cnt = (cnt + 3) >> 2;
	iret = pctx->JLINKARM_ReadDCC( (uint32_t *)pdat, cnt, (int)tms );
	if ( iret < 0 )
	{
		return iret;
	}

	if ( (uint32_t)iret != cnt )
	{
		return -1000;
	}

	// 0 - success
	return 0;
}


char * jlkarm_getregname( jcontext * pctx, uint32_t tidx )
{
	return pctx->JLINKARM_GetRegisterName( tidx );
}


*/
import "C"

type Jlink struct {
	pctx *C.jcontext
}

func TryNewJlink() (*Jlink, error) {

	var pctx *C.jcontext
	var iret C.int

	/* local base dir */
	execPath, err := os.Executable()
	if err != nil {
		return nil, errors.New("failed to resolve executable path")
	}
	execPath, err = filepath.EvalSymlinks(execPath)
	if err != nil {
		return nil, errors.New("failed to eval symlinks for executable path")
	}

	/**/
	iret = C.jlkarm_load(C.CString(filepath.Dir(execPath)), &pctx)
	if iret != 0 {
		return nil, errors.New(fmt.Sprintf("dlopen fail, %d", int32(iret)))
	}

	/**/
	return &Jlink{pctx: pctx}, nil
}

func (jk *Jlink) Open() error {
	var iret C.int

	iret = C.jlkarm_open(jk.pctx)
	if iret != 0 {
		return errors.New(fmt.Sprintf("open jlink fail, %d", int32(iret)))
	}

	return nil
}

func (jk *Jlink) Connect(spd uint32) error {
	var iret C.int

	iret = C.jlkarm_connect(jk.pctx, C.uint32_t(spd))
	if iret != 0 {
		return errors.New(fmt.Sprintf("connect device fail, %d", int32(iret)))
	}
	return nil
}

func (jk *Jlink) Close() {
	C.jlkarm_close(jk.pctx)
	return
}

func (jk *Jlink) GetVersion() uint32 {
	dv := uint32(C.jlkarm_getversion(jk.pctx))
	return dv
}

func (jk *Jlink) GetDevice() uint32 {
	dv := uint32(C.jlkarm_getdevice(jk.pctx))
	return dv
}

func (jk *Jlink) GetRegName(tidx uint32) string {
	return C.GoString(C.jlkarm_getregname(jk.pctx, C.uint32_t(tidx)))
}

func (jk *Jlink) WriteReg(tidx uint32, data uint32) {
	C.jlkarm_writereg(jk.pctx, C.uint32_t(tidx), C.uint32_t(data))
	return
}

func (jk *Jlink) ReadReg(tidx uint32) uint32 {
	dv := C.jlkarm_readreg(jk.pctx, C.uint32_t(tidx))
	return uint32(dv)
}

func (jk *Jlink) WriteMem(addr uint32, data []byte) {
	tlen := len(data)
	if tlen == 0 {
		return
	}

	ptr := C.CBytes(data)
	defer C.free(ptr)

	//
	C.jlkarm_writemem(jk.pctx, C.uint32_t(addr), C.uint32_t(tlen), ptr)
	return
}

func (jk *Jlink) ReadMem(addr uint32, tlen uint32) []byte {
	if tlen == 0 {
		return nil
	}

	ptr := C.malloc(C.size_t(tlen))
	defer C.free(ptr)

	C.jlkarm_readmem(jk.pctx, C.uint32_t(addr), C.uint32_t(tlen), ptr)
	return C.GoBytes(ptr, C.int(tlen))
}

func (jk *Jlink) Halt() int {
	iret := int(C.jlkarm_halt(jk.pctx))
	return iret
}

func (jk *Jlink) GoIntDis() {
	C.jlkarm_gointdis(jk.pctx)
	return
}

func (jk *Jlink) Step() {
	C.jlkarm_step(jk.pctx)
	return
}

// data: wirte to DCC channel;
// tms: timeout, milliSecond
func (jk *Jlink) WriteDCC(data []byte, tms uint32) error {

	tlen := len(data)
	if tlen == 0 {
		return errors.New("data len is zero")
	}

	if (tlen & 0x3) != 0 {
		return errors.New("data len is not aligned")
	}

	ptr := C.CBytes(data)
	defer C.free(ptr)

	iret := int(C.jlkarm_writedcc(jk.pctx, C.uint32_t(tlen), ptr, C.uint32_t(tms)))
	if iret != 0 {
		return errors.New("writedcc, timeout")
	}

	return nil
}

// tlen: bytes count;
// tms: timeout, milliSecond
func (jk *Jlink) ReadDCC(tlen uint32, tms uint32) ([]byte, error) {
	if tlen == 0 {
		return nil, errors.New("length is zero")
	}

	ptr := C.malloc(C.size_t(tlen))
	defer C.free(ptr)

	// 0 - success
	iret := int(C.jlkarm_readdcc(jk.pctx, C.uint32_t(tlen), ptr, C.uint32_t(tms)))
	if iret != 0 {
		return nil, errors.New("readdcc, timeout")
	}

	return C.GoBytes(ptr, C.int(tlen)), nil
}
